// Copyright (c) 2011, Jerzy Chalupski
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//     * Neither the name of the Jerzy Chalupski nor the
//       names of its contributors may be used to endorse or promote products
//       derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL JERZY CHALUPSKI BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "engine.h"

#include <QStateMachine>
#include <QEventLoop>
#include <QTimer>

namespace {
    const int KLevelThreshold = 25;
}

Engine::Engine(QObject *parent) :
        QObject(parent),
        mTick(0),
        mScore(0),
        mBrokenEggs(0),
        mGameStarted(false),
        mWolfPosition(0),
        mBrokenEggPosition(0),
        mDisplayBrokenEgg(false),
        mDisplayChicken(false),
        mRabbitVisibility(false),
        mDisplayBellAnimation(false),
        mEggSlots(4, -1),
        mCurrentAction(0),
        mSettings("Chalup", "Nu Pagadi")
{
    mActions << MoveEggThree << MoveEggTwo << MoveEggOne << DoNothing << MoveEggZero << DoNothing << GenerateNewEgg << DoNothing;

    QStateMachine *machine = new QStateMachine(this);
    QState *menuState = new QState(machine);
    QState *initGameState = new QState(machine);
    QState *gameState = new QState(machine);
    QState *idleState = new QState(gameState);
    QState *actionState = new QState(gameState);
    QState *brokenEggState = new QState(gameState);
    QState *lifeLossState = new QState(gameState);
    QState *randomizeRabbitState = new QState(gameState);
    QState *ringBellState = new QState(gameState);
    QState *levelUpState = new QState(gameState);
    QState *gameOverState = new QState(machine);

    machine->setInitialState(menuState);
    gameState->setInitialState(idleState);

    menuState->addTransition(this, SIGNAL(newGameClicked()), initGameState);
    menuState->assignProperty(this, "gameStarted", false);
    menuState->assignProperty(this, "rabbitVisibility", false);
    menuState->assignProperty(this, "brokenEggs", 0);
    connect(menuState, SIGNAL(entered()), SLOT(showHighscore()));
    connect(menuState, SIGNAL(entered()), SLOT(clearEggRows()));

    initGameState->addTransition(gameState);
    initGameState->assignProperty(this, "score", 0);
    initGameState->assignProperty(this, "rabbitVisibility", true);
    initGameState->assignProperty(this, "tick", 25);
    connect(initGameState, SIGNAL(entered()), SLOT(startGame()));

    gameState->addTransition(this, SIGNAL(gameOver()), gameOverState);
    gameState->assignProperty(this, "gameStarted", true);

    idleState->addTransition(&mHeartbeat, SIGNAL(timeout()), actionState);

    connect(actionState, SIGNAL(entered()), SLOT(gameHeartbeat()));
    actionState->addTransition(this, SIGNAL(scoreThresholdReached()), ringBellState);
    actionState->addTransition(this, SIGNAL(scoreChanged(int)), randomizeRabbitState);
    actionState->addTransition(this, SIGNAL(brokenEggPositionChanged(int)), brokenEggState);
    actionState->addTransition(this, SIGNAL(backToIdle()), idleState);

    ringBellState->assignProperty(this, "displayBellAnimation", true);
    ringBellState->addTransition(this, SIGNAL(bellAnimationFinished()), levelUpState);

    connect(levelUpState, SIGNAL(entered()), SLOT(levelUp()));
    levelUpState->assignProperty(this, "displayBellAnimation", false);
    levelUpState->addTransition(randomizeRabbitState);

    brokenEggState->addTransition(this, SIGNAL(lifeLossAnimationFinished()), lifeLossState);

    connect(lifeLossState, SIGNAL(entered()), SLOT(decreaseLife()));
    lifeLossState->assignProperty(this, "brokenEggPosition", 0);
    lifeLossState->assignProperty(this, "displayBrokenEgg", false);
    lifeLossState->assignProperty(this, "displayChicken", false);
    lifeLossState->addTransition(this, SIGNAL(gameOver()), gameOverState);
    lifeLossState->addTransition(randomizeRabbitState);

    connect(randomizeRabbitState, SIGNAL(entered()), SLOT(randomizeRabbit()));
    randomizeRabbitState->addTransition(idleState);

    gameOverState->addTransition(menuState);
    gameOverState->assignProperty(this, "gameStarted", false);
    connect(gameOverState, SIGNAL(entered()), SLOT(saveHighscore()));

    // game heartbeat
    mHeartbeat.setSingleShot(true);
    connect(this, SIGNAL(tickChanged(int)), SLOT(adjustHeartBeat()));
    connect(idleState, SIGNAL(entered()), &mHeartbeat, SLOT(start()));

    machine->start();
}

void Engine::gameHeartbeat()
{
    switch (mActions[mCurrentAction]) {
        case DoNothing: break;
        case GenerateNewEgg:
        {
            Q_ASSERT(mEggSlots[0] == -1);
            mEggSlots[0] = qrand() % 4;
            emit eggsStateChanged();
            break;
        }
        default:
        {
            Action currentEggSlot = mActions[mCurrentAction];
            if (mEggSlots[currentEggSlot] == -1) break;

            const int roostNr = mEggSlots[currentEggSlot];
            mEggSlots[currentEggSlot] = -1;

            if (mActions[mCurrentAction] == MoveEggThree) {
                emit eggsStateChanged();

                if (roostNr == wolfPosition()) {
                    if (++mScore % KLevelThreshold == 0) {
                        emit scoreThresholdReached();
                    }
                    emit scoreChanged(mScore);
                } else {
                    setDisplayBrokenEgg(true);
                    setDisplayChicken(rabbitVisibility());
                    setBrokenEggPosition(roostNr <= 1 ? -1 : 1);
                }
            } else {
                Q_ASSERT(mEggSlots[currentEggSlot+1] == -1);
                mEggSlots[currentEggSlot+1] = roostNr;
                emit eggsStateChanged();
            }
        }
    }
    mCurrentAction = (++mCurrentAction) % mActions.count();
    emit backToIdle();
}

// ---------------------------------------------------------------------------
//  Property getters and setters
// ---------------------------------------------------------------------------
//
int Engine::tick() const
{
    return mTick;
}

void Engine::setTick(int value)
{
    if (value == mTick) return;
    mTick = value;
    emit tickChanged(value);
}

int Engine::score() const
{
    return mScore;
}

void Engine::setScore(int value)
{
    if (value == mScore) return;
    mScore = value;
    emit scoreChanged(value);
}

int Engine::eggsState() const
{
    int eggsState(0);
    for (int position(0); position != mEggSlots.count(); ++position) {
        const int roostAtPosition(mEggSlots.at(position));
        if (roostAtPosition != -1) {
            eggsState |= (1 << (position + roostAtPosition * 4));
        }
    }
    return eggsState;
}

int Engine::brokenEggs() const
{
    return mBrokenEggs;
}

void Engine::setBrokenEggs(int value)
{
    if (value == mBrokenEggs) return;
    mBrokenEggs = value;
    emit brokenEggsChanged(value);
}

bool Engine::gameStarted() const
{
    return mGameStarted;
}

void Engine::setGameStarted(bool value)
{
    if (value == mGameStarted) return;
    mGameStarted = value;
    emit gameStartedChanged(value);
}

int Engine::wolfPosition() const
{
    return mWolfPosition;
}

void Engine::setWolfPosition(int newPosition)
{
    if (newPosition == mWolfPosition) return;
    mWolfPosition = newPosition;
    emit wolfPositionChanged(newPosition);
}

int Engine::brokenEggPosition() const
{
    return mBrokenEggPosition;
}

void Engine::setBrokenEggPosition(int value)
{
    if (value == mBrokenEggPosition) return;
    mBrokenEggPosition = value;
    emit brokenEggPositionChanged(value);
}

bool Engine::displayBrokenEgg() const
{
    return mDisplayBrokenEgg;
}

void Engine::setDisplayBrokenEgg(bool value)
{
    if (value == mDisplayBrokenEgg) return;
    mDisplayBrokenEgg = value;
    emit displayBrokenEggChanged(value);
}

bool Engine::displayChicken() const
{
    return mDisplayChicken;
}

void Engine::setDisplayChicken(bool value)
{
    if (value == mDisplayChicken) return;
    mDisplayChicken = value;
    emit displayChickenChanged(value);
}

bool Engine::rabbitVisibility() const
{
    return mRabbitVisibility;
}

void Engine::setRabbitVisibility(bool value)
{
    if (value == mRabbitVisibility) return;
    mRabbitVisibility = value;
    emit rabbitVisibilityChanged(value);
}

bool Engine::displayBellAnimation() const
{
    return mDisplayBellAnimation;
}

void Engine::setDisplayBellAnimation(bool value)
{
    if (value == mDisplayBellAnimation) return;
    mDisplayBellAnimation = value;
    emit displayBellAnimationChanged(value);
}

// ---------------------------------------------------------------------------
//  Other property helper methods
// ---------------------------------------------------------------------------
//

int Engine::highscore() const
{
    int val = mSettings.value("Highscore").toInt();
    return val;
}

void Engine::decreaseLife()
{
    mBrokenEggs += rabbitVisibility() ? 1 : 2;
    emit brokenEggsChanged(mBrokenEggs);
    if (mBrokenEggs >= 6){
        emit gameOver();
    }
}

void Engine::startGame()
{
    mCurrentAction = 0;
}

void Engine::clearEggRows()
{
    mEggSlots.fill(-1);
    emit eggsStateChanged();
}

void Engine::showHighscore()
{
    mScore = highscore();
    emit scoreChanged(mScore);
}

void Engine::saveHighscore()
{
    if (mScore > highscore()) {
        mSettings.setValue("Highscore", mScore);
    }
}

void Engine::adjustHeartBeat()
{
    mHeartbeat.setInterval(tick() * 5);
}

void Engine::randomizeRabbit()
{
    if (qrand() % 3) {
        setRabbitVisibility(!rabbitVisibility());
    }
}

void Engine::levelUp()
{
    setBrokenEggs(qMax(0, brokenEggs() - 2));
    setTick(qreal(tick()) * 0.9);
}
